import functools
from types import FunctionType

from django.db.models import ForeignKey, ManyToManyField
from django.urls import re_path
from django.shortcuts import HttpResponse, render, redirect
from django.urls import reverse
from django.utils.safestring import mark_safe
from django.http import QueryDict
from django import forms


class StarkModelForm(forms.ModelForm):
    """设置modelform样式的类"""

    def __init__(self, *args, **kwargs):
        super(StarkModelForm, self).__init__(*args, **kwargs)
        # 统一给modelform生成字段样式
        for name, field in self.fields.items():
            field.widget.attrs['class'] = 'form-control'


class StarkForm(forms.Form):
    """设置form样式的类"""

    def __init__(self, *args, **kwargs):
        super(StarkForm, self).__init__(*args, **kwargs)
        # 统一给modelform生成字段样式
        for name, field in self.fields.items():
            field.widget.attrs['class'] = 'form-control'


# 对于stark组件中定义列时，choice如果想要显示中文信息，调用此方法即可
def get_choice_text(title, field, *args, **kwargs):
    """
     处理表中的choice字段显示中文的方法
    :param title: 页面现实的表头
    :param field: 数据库的字段值
    :return:
    """

    def inner(self, obj=None, is_header=None, *args, **kwargs):
        if is_header:
            return title
        method = 'get_%s_display' % field
        return getattr(obj, method)()  # 获取choice下的中文名

    return inner


def get_date_format_text(title, field, format='%Y-%m-%d'):
    """
     处理日期格式化显示
    :param title: 页面现实的表头
    :param field: 数据库的字段值
    :return:
    """

    def inner(self, obj=None, is_header=None, *args, **kwargs):
        if is_header:
            return title
        date_time_value = getattr(obj, field)
        return date_time_value.strftime(format)  # 获取转换后的时间戳

    return inner


def get_m2m_text(title, field):
    """
     获取多对多关系的字段文本
    :param title: 页面现实的表头
    :param field: 数据库的字段值
    :return:
    """

    def inner(self, obj=None, is_header=None):
        if is_header:
            return title
        queryset = getattr(obj, field).all()
        text_list = [str(row) for row in queryset]
        return ','.join(text_list)

    return inner


# 组合搜索界面展示 后端判断是元组 还是queryset
class SearchGroupRow(object):
    def __init__(self, title, queryset_or_tuple, option, queryset_dict):
        self.queryset_or_tuple = queryset_or_tuple
        self.option = option
        self.title = title
        self.queryset_dict = queryset_dict

    def __iter__(self):  # 让一个对象变成可迭代对象
        """变成可迭代对象"""
        # 在后端判断 获取到的时tuple 还是 queryset
        # if isinstance(self.queryset_or_tuple, tuple):
        #     # 元组则获取第一个元素
        #     for item in self.queryset_or_tuple:
        #         yield "<a href="">%s</a>" % item[1]
        # else:
        #     # queryset则获取指定的字段的属性
        #     for item in self.queryset_or_tuple:
        #         yield "<a href="">%s</a>" % item
        yield "<div class='whole'>"
        yield self.title
        yield "</div>"
        yield "<div class='others' style='margin-left:80px'>"
        # 没有筛选条件则全部默认被选中
        total_queryset_dict = self.queryset_dict.copy()
        total_queryset_dict._mutable = True
        if not self.queryset_dict.getlist(self.option.field):
            yield "<a class= 'active' href='?%s'>全部</a>" % total_queryset_dict.urlencode()
        else:
            total_queryset_dict.pop(self.option.field)
            yield "<a href='?%s'>全部</a>" % total_queryset_dict.urlencode()
        for item in self.queryset_or_tuple:
            text = self.option.get_text(item)

            """生成按钮url"""
            # 需要request.GET  QueryDict: {'gender': ['1'], 'depart': ['2']}
            value = str(self.option.get_value(item))
            queryset_dict = self.queryset_dict.copy()
            queryset_dict._mutable = True
            origin_value_list = queryset_dict.getlist(self.option.field)  # 获取当前的get列表

            if not self.option.is_multi:
                """ 如果不支持多选"""
                queryset_dict[self.option.field] = value
                if str(value) in origin_value_list:
                    # 获取组合搜索按钮文本背后对应的值
                    # 默认被选中
                    # 选中再次点的时候，应该取消选中
                    queryset_dict.pop(self.option.field)
                    yield "<a class='active 'href='?%s'>%s</a>" % (queryset_dict.urlencode(), text)
                else:
                    yield "<a href='?%s'>%s</a>" % (queryset_dict.urlencode(), text)
            else:
                """支持多选"""
                multi_value_list = queryset_dict.getlist(self.option.field)
                if value in multi_value_list:
                    multi_value_list.remove(value)
                    queryset_dict.setlist(self.option.field, multi_value_list)
                    yield "<a class='active 'href='?%s'>%s</a>" % (queryset_dict.urlencode(), text)
                else:
                    multi_value_list.append(value)
                    queryset_dict.setlist(self.option.field, multi_value_list)
                    yield "<a href='?%s'>%s</a>" % (queryset_dict.urlencode(), text)

        yield "</div>"


# 组合搜索附加条件
class SearchGroupOption(object):
    # 初始化参数，关键字段、和字典形式的搜索条件 {'id_gt':2}
    def __init__(self, field, is_multi=False, db_condition=None, text_func=None, value_func=None):
        """
        :param field: 搜索字段名
        :param is_multi: 是否支持多选
        :param db_condition: 组合搜索条条件
        :param text_func:用于显示组合搜索按钮页面文本，可以自定义
        :param value_func:文本背后对应的值
        """
        self.field = field
        self.is_multi = is_multi
        if not db_condition:
            db_condition = {}
        self.db_condition = db_condition
        self.text_func = text_func
        self.value_func = value_func
        self.is_choice = False

    def get_db_condition(self, request, *args, **kwargs):
        """方便重写方法，自定义筛选条件"""
        return self.db_condition

    def get_queryset_or_tuple(self, model_class, request, *args, **kwargs):
        """
        根据字段去获取关联的数据
        :return:
        """
        # 根据列表元素，找到字段对应的对象，再根据对象找到对应的关联数据
        field_obj = model_class._meta.get_field(self.field)  # 字段对象 gender='xxxx'
        title = field_obj.verbose_name
        # print(item,field_obj) # gender app01.UserInfo.gender
        # 判断 属于哪个类型的对象
        if isinstance(field_obj, ForeignKey) or isinstance(field_obj, ManyToManyField):
            # 获取FK 和M2M 关联的数据
            # obj = field_obj.related_model.objects.all()
            obj = field_obj.remote_field.model.objects.filter(**self.db_condition)
            return SearchGroupRow(title, obj, self, request.GET)
        else:
            # 获取 choice 字段对象中的数据
            self.is_choice = True
            return SearchGroupRow(title, field_obj.choices, self, request.GET)

    def get_text(self, field_obj):
        """
        获取文本的函数
        :param field_obj:
        :return:
        """
        if self.text_func:
            return self.text_func(field_obj)
        if self.is_choice:
            return field_obj[1]
        else:
            return field_obj

    def get_value(self, field_obj):
        """
        获取文本背后的值
        :param field_obj:
        :return:
        """
        if self.value_func:
            return self.value_func(field_obj)
        if self.is_choice:
            return field_obj[0]
        else:
            return field_obj.pk


# 处理视图及url
class StarkHandler(object):
    field_name_list = []  # 显示的字段的名字

    def __init__(self, site, model_class, prev):
        self.site = site
        self.model_class = model_class
        self.prev = prev
        self.request = None

    has_add_btn = True
    model_form_class = None

    order_list = []  # 根据列表内元素排序
    search_list = []  # 定义 关键字搜索列表
    action_list = []  # 父类的批量操作列表
    search_group = []  # 组合搜索关键字段列表

    def multi_delete(self, request):
        """批量删除"""
        pk_list = request.POST.getlist('pk')  # 获取checkbox 的value值，即pk的值
        self.model_class.objects.filter(id__in=pk_list).delete()

    def multi_init(self, request):
        """批量初始化"""
        pass

    def get_action_list(self):
        """获取批量操作列表"""
        return self.action_list

    def get_order_list(self):
        """处理排序"""
        if self.order_list:
            return self.order_list
        return ['id', ]  # 如果order_list 没有值 则根据id从小到大默认排序

    def get_search_list(self):
        """处理搜索"""
        return self.search_list

    def save(self, request, form, is_update, *args, **kwargs):
        """
        使用modelform保存数据之前，预留的钩子方法
        用于 指定字段保存，考虑没有显示的字段不能为空，需要添加默认值
        :param form: form对象
        :param is_update:
        :return:
        """
        form.save()

    def get_model_form_class(self, is_add, request, pk, *args, **kwargs):
        """获取form表单样式"""
        if self.model_form_class:
            return self.model_form_class

        class DynamicModelForm(StarkModelForm):
            class Meta:
                model = self.model_class
                fields = '__all__'

        return DynamicModelForm

    def get_add_btn(self, request, *args, **kwargs):
        """根据用户权限，显示添加按钮"""
        if self.has_add_btn:
            add_url = self.reverse_add_url(*args, **kwargs)
            return '<a href="%s" style="margin-left:10px" class="btn btn-primary">添加</a>' % add_url
        return None

    def display_checkbox(self, obj=None, is_header=None, *args, **kwargs):
        """批量选择字段"""
        if is_header:
            return '选择'
        return mark_safe('<input type="checkbox" name="pk" value="%s"  />' % obj.pk)

    def display_edit(self, obj=None, is_header=None, *args, **kwargs):
        if is_header:
            return '编辑操作'
        # name = '%s:%s' % (self.site.namespace, self.get_edit_url_name)
        # url = reverse(name, args=(obj.pk,))  # 反向解析url地址
        edit_url = self.reverse_edit_url(pk=obj.pk)
        return mark_safe('<a href="%s">编辑</a>' % edit_url)  # 跳转到对应的编辑页面

    def display_del(self, obj=None, is_header=None, *args, **kwargs):
        if is_header:
            return '删除操作'
        # name = '%s:%s' % (self.site.namespace, self.get_del_url_name)
        # url = reverse(name, args=(obj.pk,))  # 反向解析url地址
        del_url = self.reverse_del_url(pk=obj.pk)
        return mark_safe('<a href="%s" >删除</a>' % del_url)  # 跳转到对应的编辑页面

    def display_edit_del(self, obj=None, is_header=None, *args, **kwargs):
        """编辑删除 在一列显示"""
        if is_header:
            return '操作'
        tmp = '<a href="%s" class="glyphicon glyphicon-edit" style="color:darkgrey"></a> &nbsp;&nbsp;<a href="%s" ' \
              'class="glyphicon glyphicon-trash" style="color:red"></a>' % (
                  self.reverse_edit_url(pk=obj.pk), self.reverse_del_url(pk=obj.pk))
        return mark_safe(tmp)

    def get_field_name_list(self, request, *args, **kwargs):

        # 为当前也显示的列进行扩展需求
        value = []
        if self.field_name_list:
            value.extend(self.field_name_list)
            value.append(type(self).display_edit_del)  # 添加默认显示的操作字段： 编辑和删除
        return value

    def get_search_group(self):
        return self.search_group

    def get_search_group_condition(self, request):
        """获取组合搜索的关键字条件"""
        condition = {}
        for option in self.get_search_group():
            # 判断是否时多选，多选 用 __in  单选就是 =
            if option.is_multi:
                value_list = request.GET.getlist(option.field)
                if not value_list:
                    continue
                condition["%s__in" % option.field] = value_list
            else:
                value = request.GET.get(option.field)
                if not value:
                    continue
                condition[option.field] = value
        return condition

    def get_queryset(self, request, *args, **kwargs):
        """获取公共的搜索queryset"""
        return self.model_class.objects

    def changelist_view(self, request, *args, **kwargs):
        """
        列表页面
        :param request: 请求体对象
        :return:
        """
        """批量操作"""
        action_list = self.get_action_list()
        # 因为 函数名传递给模板中会自动执行这个函数，所以要制作成以函数命名为key，函数text为value的字典
        action_dict = {func.__name__: func.text for func in action_list}  # 传递给模板文件，循环显示

        if request.method == 'POST':
            # 根据post请求 获取action 名字
            action_func_name = request.POST.get('action')
            # print(action_func_name)
            # 再根据 名字判断是否在action_list中
            if action_func_name and action_func_name in action_dict:
                func = getattr(self, action_func_name)  # 执行对应的action函数
                func(request, *args, **kwargs)

        search_list = self.get_search_list()
        """
        1.如果search_list 无值，则不显示搜索框
        2.获取搜索框关键字      
        """
        search_value = request.GET.get('q', '')
        # 通过关键字 和search_list 里的元素匹配包含
        from django.db.models import Q  # 用于构造复杂的ORM查询条件
        conn = Q()  # 生成Q对象
        conn.connector = 'OR'  # 设置条件为或
        for item in search_list:
            conn.children.append((item, search_value))  # 进行判断，判断 tom是否在name里
        # data_list = self.model_class.objects.filter(conn).order_by(*order_list)
        # 查询的时候 就用 filter(conn)

        """处理表格"""
        # 1. 获取的表头
        field_name_list = self.get_field_name_list(request, *args, **kwargs)
        header_list = []
        if field_name_list:
            for key_or_func in field_name_list:
                if type(key_or_func) != str:
                    verbose_name = key_or_func(self, obj=None, is_header=True, )
                else:
                    verbose_name = self.model_class._meta.get_field(key_or_func).verbose_name
                header_list.append(verbose_name)
        else:
            header_list.append(self.model_class._meta.model_name)
        order_list = self.get_order_list()
        data_list = self.get_queryset(request, *args, **kwargs).filter(conn).filter(
            **self.get_search_group_condition(request)).order_by(
            *order_list)  # 获取当前表的数据
        # [obj1,obj2,obj3...]
        """处理表格内容"""
        tr_list = []
        for row in data_list:
            row_list = []
            # 将每行字段的值添加到列表中
            if field_name_list:
                for key_or_func in field_name_list:  # obj['key']
                    # 根据显示的字段，获取对应的值
                    if type(key_or_func) != str:
                        # if isinstance(key_or_func, FunctionType):
                        # 判断列表字段是否是函数，如果是则执行函数
                        row_list.append(key_or_func(self, row, is_header=False, *args, **kwargs))
                    else:
                        row_list.append(getattr(row, key_or_func, ))  # ['tom','18','123@qq.com']
            else:
                row_list.append(row)
            tr_list.append(row_list)
        # 获取添加按钮
        add_btn = self.get_add_btn(request, *args, **kwargs)

        """ 组合搜索 """
        search_group_row_list = []
        search_group = self.get_search_group()  # ['gender','depart']
        for search_option_obj in search_group:
            row = search_option_obj.get_queryset_or_tuple(self.model_class, request)
            search_group_row_list.append(row)
        return render(request, 'stark/data_list.html',
                      {'data_list': data_list,
                       'header_list': header_list,
                       'tr_list': tr_list,
                       'add_btn': add_btn,
                       'search_list': self.search_list,
                       'search_value': search_value,
                       'action_dict': action_dict,
                       'search_group_row_list': search_group_row_list}
                      )

    def add_view(self, request, *args, **kwargs):
        """
        添加页面
        :param request:
        :return:
        """

        model_form_class = self.get_model_form_class(True, request, None, *args, **kwargs)
        form = model_form_class()
        if request.method == 'POST':
            form = model_form_class(data=request.POST)
            if form.is_valid():
                self.save(request, form, is_update=False, *args, **kwargs)
                # 数据库保存成功后，跳转到列表页面，应该返回之前带有搜索条件的url
                url = self.redirect_url(*args, **kwargs)
                return redirect(url)
            else:
                return render(request, 'stark/change.html', {'forms': form})
        return render(request, 'stark/change.html', {'forms': form})

    def get_edit_object(self, request, pk, *args, **kwargs):
        return self.model_class.objects.filter(id=pk).first()

    def edit_view(self, request, pk, *args, **kwargs):
        """
        编辑界面
        :param request:
        :param pk:
        :return:
        """
        current_edit_obj = self.get_edit_object(request, pk, *args, **kwargs)
        if not current_edit_obj:
            return HttpResponse('要修改的数据不存在,请重新选择')
        model_form_class = self.get_model_form_class(False, request, pk)
        form = model_form_class(instance=current_edit_obj)  # 显示默认值
        if request.method == 'POST':
            form = model_form_class(data=request.POST, instance=current_edit_obj)
            if form.is_valid():
                self.save(request, form, is_update=False, *args, **kwargs)
                # 数据库保存成功后，跳转到列表页面，应该返回之前带有搜索条件的url
                url = self.redirect_url(*args, **kwargs)
                return redirect(url)
            else:
                return render(request, 'stark/change.html', {'forms': form})
        return render(request, 'stark/change.html', {'forms': form})

    def get_del_object(self, request, pk, *args, **kwargs):
        return self.model_class.objects.filter(id=pk).delete()

    def del_view(self, request, pk, *args, **kwargs):
        """
        删除页面
        :param request:
        :param pk:
        :return:
        """
        cancel = self.redirect_url(*args, **kwargs)
        if request.method == 'POST':
            self.get_del_object(request, pk, *args, **kwargs)
            return redirect(cancel)
        return render(request, 'stark/delete.html', {'cancel': cancel})

    def get_urls_name(self, param):
        if self.prev:
            return '%s_%s_%s_%s' % (
                self.model_class._meta.app_label, self.model_class._meta.model_name, self.prev, param)
        return '%s_%s_%s' % (self.model_class._meta.app_label, self.model_class._meta.model_name, param)

    @property
    def get_list_url_name(self):
        """获取列表页面url的name"""
        return self.get_urls_name('list')

    @property
    def get_add_url_name(self):
        return self.get_urls_name('add')

    @property
    def get_edit_url_name(self):
        return self.get_urls_name('edit')

    @property
    def get_del_url_name(self):
        return self.get_urls_name('del')

    def reverse_commons(self, name, *args, **kwargs):
        """
        反向生成url公共方法
        :param name: 视图url别名
        :param args:
        :param kwargs:
        :return:
        """
        name = '%s:%s' % (self.site.namespace, name)
        base_url = reverse(name, args=args, kwargs=kwargs)
        if not self.request.GET:  # 如果没有搜索条件，则用原始的url
            url = base_url
        else:
            param = self.request.GET.urlencode()  # 获取get请求体参数
            new_query_dict = QueryDict(mutable=True)
            new_query_dict['_filter'] = param
            # 将原页面的参数 拼接到新的页面后面
            url = '%s?%s' % (base_url, new_query_dict.urlencode())
        return url

    def reverse_add_url(self, *args, **kwargs):
        """
        反向生成携带当前搜索条件的添加页面url
        :return:
        """
        # 根据别名反向成成url，并且要携带当前参数
        return self.reverse_commons(self.get_add_url_name, *args, **kwargs)

    def reverse_edit_url(self, *args, **kwargs):
        """
        反向生成携带当前搜索条件的编辑页面url
        :return:
        """
        # 根据别名反向成成url，并且要携带当前参数
        return self.reverse_commons(self.get_edit_url_name, *args, **kwargs)

    def reverse_del_url(self, *args, **kwargs):
        """
        反向生成携带当前搜索条件的删除页面url
        :return:
        """
        # 根据别名反向成成url，并且要携带当前参数
        return self.reverse_commons(self.get_del_url_name, *args, **kwargs)

    def redirect_url(self, *args, **kwargs):
        """
        解析url后的参数，拼接返回原搜索条件下的url
        :return:
        """
        name = '%s:%s' % (self.site.namespace, self.get_list_url_name)
        base_url = reverse(name, args=args, kwargs=kwargs)
        param = self.request.GET.get('_filter')
        if not param:
            return base_url
        url = '%s?%s' % (base_url, param)
        return url

    # 通过闭包函数 给每个视图的request 赋值
    def wrapper(self, func):
        @functools.wraps(func)
        def inner(request, *args, **kwargs):
            self.request = request
            return func(request, *args, **kwargs)

        return inner

    # 获取url
    def get_urls(self):
        app_name = self.model_class._meta.app_label  # 获取当前类app名称
        model_name = self.model_class._meta.model_name  # 获取当前表名
        if self.prev:
            patterns = [
                re_path('list/', self.wrapper(self.changelist_view), name=self.get_list_url_name),
                re_path('add/', self.wrapper(self.add_view), name=self.get_add_url_name),
                re_path('edit/(?P<pk>\d+)/', self.wrapper(self.edit_view), name=self.get_edit_url_name),
                re_path('del/(?P<pk>\d+)/', self.wrapper(self.del_view), name=self.get_del_url_name),
            ]
        else:
            patterns = [
                re_path('list/', self.wrapper(self.changelist_view), name='%s_%s_list' % (app_name, model_name)),
                re_path('add/', self.wrapper(self.add_view), name='%s_%s_add' % (app_name, model_name)),
                re_path('edit/(?P<pk>\d+)/', self.wrapper(self.edit_view), name='%s_%s_edit' % (app_name, model_name)),
                re_path('del/(?P<pk>\d+)/', self.wrapper(self.del_view), name='%s_%s_del' % (app_name, model_name)),
            ]
        patterns.extend(self.extra_urls())  # 新增的url 扩增
        return patterns

    def extra_urls(self):  # 用于额外增加的url
        return []


class StarkSite(object):
    """动态生成url"""

    def __init__(self):
        self._register = []
        self.app_name = 'stark'
        self.namespace = 'stark'

    def register(self, model_class, handler_class=None, prev=None):
        """
        :param handler_class: 请求处理的视图函数所在的类
        :param model_class: app/models.py 数据库相关的类
        :return:
        """
        if not handler_class:
            handler_class = StarkHandler
        self._register.append(
            {'model_class': model_class, 'handler': handler_class(self, model_class, prev), 'prev': prev})

    def get_urls(self):
        patterns = []
        for item in self._register:
            # 生成url路径  app01/depart/list/  # app名/表名/视图类型
            app_name = item['model_class']._meta.app_label  # 获取当前类app名称
            model_name = item['model_class']._meta.model_name  # 获取当前表名
            handler = item['handler']
            prev = item['prev']
            if prev:
                # patterns.append(path('%s/%s/ist/' % (app_name, model_name), handler.changelist_view))
                # patterns.append(path('%s/%s/add/' % (app_name, model_name), handler.add_view))
                # patterns.append(path('%s/%s/edit/(\d+)/' % (app_name, model_name), handler.edit_view))
                # patterns.append(path('%s/%s/del/(\d+)/' % (app_name, model_name), handler.del_view))
                # 对url再次进行分发，提取相同的部分
                patterns.append((re_path('%s/%s/%s/' % (app_name, model_name, prev), (handler.get_urls(), None, None))))
            else:
                patterns.append((re_path('%s/%s/' % (app_name, model_name,), (handler.get_urls(), None, None))))
        return patterns

    @property
    def urls(self):
        """
        stark/ app01/depart/list/
        stark/ app01/depart/add/
        stark/ app01/depart/edit/(\d+)/
        stark/ app01/depart/del/(\d+)/
        :return:
        """
        return self.get_urls(), self.app_name, self.namespace


site = StarkSite()
